🎬 Event-API Entwicklung

Movie API mit Django Rest Framework

Schritt-für-Schritt Tutorial

Von der Installation bis zur fertigen REST-API

🎯 Was bauen wir?

Eine vollständige REST-API für Filme & Künstler

3 Models, ViewSets, Custom Actions, Filtering

📦 Models

  • Movie: Filme mit Details
  • Artist: Schauspieler/Künstler
  • MovieCasting: Besetzung (Many-to-Many)

🔌 API Endpoints

  • CRUD für alle Models
  • Filtering & Search
  • Custom Actions
  • Pagination

✨ Features

  • Top-Rated Movies
  • Recent Movies
  • Movie Castings
  • Artist Filmography

📋 Voraussetzungen

Was du bereits haben solltest:

  • Python 3.8+ installiert
  • Django Projekt erstellt
  • movies App angelegt
  • Models definiert (Movie, Artist, MovieCasting)
  • Migrations durchgeführt

Projektstruktur:

movieapi/
├── movieapi/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── movies/
│   ├── __init__.py
│   ├── models.py       # ✅ Models vorhanden
│   ├── admin.py
│   ├── views.py        # ← Hier arbeiten wir!
│   └── apps.py
└── manage.py

💡 Falls du noch kein Projekt hast:

# Neues Projekt erstellen:
django-admin startproject movieapi .
python manage.py startapp movies

# Models kopieren (wie in der Aufgabe gegeben)
# Dann Migrationen:
python manage.py makemigrations
python manage.py migrate

📦 Schritt 1: Django Rest Framework installieren

1.1

DRF via pip installieren

# Terminal (virtuelle Umgebung aktiviert):
pip install djangorestframework

# Output:
Collecting djangorestframework
  Downloading djangorestframework-3.14.0-py3-none-any.whl
Installing collected packages: djangorestframework
Successfully installed djangorestframework-3.14.0
1.2

Überprüfung: Version anzeigen

# Terminal:
pip show djangorestframework

# Output:
Name: djangorestframework
Version: 3.14.0
Summary: Web APIs for Django, made easy.
Home-page: https://www.django-rest-framework.org/
Author: Tom Christie
License: BSD

✅ Erfolg!

Django Rest Framework ist jetzt installiert.

⚙️ Schritt 2: DRF konfigurieren

settings.py öffnen und DRF hinzufügen:

# filepath: movieapi/settings.py

# ...existing code...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    # Third-party Apps
    'rest_framework',  # ← NEU! DRF hinzufügen
    
    # Eigene Apps
    'movies',  # ← Sollte bereits vorhanden sein
]

# ...existing code...

DRF Settings hinzufügen (am Ende der Datei):

# filepath: movieapi/settings.py

# ...existing code...

# Django Rest Framework Configuration
REST_FRAMEWORK = {
    # Permissions (für Development erstmal offen)
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
    
    # Renderer (JSON + Browsable API)
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    
    # Pagination
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
}

💡 Was haben wir konfiguriert?

  • AllowAny: Keine Authentication (Development)
  • JSONRenderer: API gibt JSON zurück
  • BrowsableAPIRenderer: HTML-Interface im Browser
  • Pagination: 10 Einträge pro Seite

🔄 Schritt 3: Serializers erstellen

Serializers = Daten-Übersetzer

Konvertieren Models ↔ JSON

Neue Datei erstellen: movies/serializers.py

# filepath: movies/serializers.py
from rest_framework import serializers
from .models import Movie, Artist, MovieCasting


class MovieSerializer(serializers.ModelSerializer):
    """
    Serializer für Movie Model
    
    Konvertiert Movie-Objekte zu JSON und umgekehrt
    """
    class Meta:
        model = Movie
        fields = '__all__'  # Alle Felder
        read_only_fields = ['created_at', 'updated_at']


class ArtistSerializer(serializers.ModelSerializer):
    """
    Serializer für Artist Model
    
    Inkludiert das full_name Property
    """
    full_name = serializers.ReadOnlyField()  # Property aus Model
    
    class Meta:
        model = Artist
        fields = '__all__'
        read_only_fields = ['created_at', 'updated_at']


class MovieCastingSerializer(serializers.ModelSerializer):
    """
    Serializer für MovieCasting Model
    
    Einfache Version nur mit IDs
    """
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']


class MovieCastingDetailSerializer(serializers.ModelSerializer):
    """
    Detaillierter Casting Serializer
    
    Mit verschachtelten Movie & Artist Objekten
    """
    movie = MovieSerializer(read_only=True)
    artist = ArtistSerializer(read_only=True)
    
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']

📚 Serializer - Was macht das?

📥 Serialisierung (Model → JSON)

# Python Model:
movie = Movie.objects.get(pk=1)
# Movie(title="The Matrix", year=1999)

# Serializer:
serializer = MovieSerializer(movie)

# JSON Output:
serializer.data
# {
#   "id": 1,
#   "title": "The Matrix",
#   "year": 1999,
#   "genre": "Sci-Fi",
#   "rating": "8.7",
#   "description": "...",
#   "created_at": "2024-01-15T10:00:00Z",
#   "updated_at": "2024-01-15T10:00:00Z"
# }

📤 Deserialisierung (JSON → Model)

# JSON Input:
data = {
    "title": "Inception",
    "year": 2010,
    "genre": "Sci-Fi",
    "rating": "8.8",
    "description": "A thief..."
}

# Serializer:
serializer = MovieSerializer(data=data)

# Validierung:
if serializer.is_valid():
    # Speichern:
    movie = serializer.save()
    # Movie in DB gespeichert!
else:
    # Fehler anzeigen:
    print(serializer.errors)

✨ Was bietet der Serializer?

  • Automatische Validierung: Prüft Datentypen & Constraints
  • Read-Only Felder: created_at, updated_at nicht änderbar
  • Properties: full_name aus Model verfügbar
  • Verschachtelung: MovieCastingDetailSerializer mit Movie & Artist

🎯 Schritt 4: ViewSets erstellen

ViewSets = Alle CRUD in einer Klasse

Minimaler Code, maximale Funktionalität

views.py öffnen und ViewSets erstellen:

# filepath: movies/views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Movie, Artist, MovieCasting
from .serializers import (
    MovieSerializer,
    ArtistSerializer,
    MovieCastingSerializer,
    MovieCastingDetailSerializer
)


class MovieViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Movie Model
    
    Automatisch verfügbar:
    - list: GET /api/movies/
    - create: POST /api/movies/
    - retrieve: GET /api/movies/{id}/
    - update: PUT /api/movies/{id}/
    - partial_update: PATCH /api/movies/{id}/
    - destroy: DELETE /api/movies/{id}/
    """
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer


class ArtistViewSet(viewsets.ModelViewSet):
    """
    ViewSet für Artist Model
    
    Alle CRUD-Operationen automatisch verfügbar
    """
    queryset = Artist.objects.all()
    serializer_class = ArtistSerializer


class MovieCastingViewSet(viewsets.ModelViewSet):
    """
    ViewSet für MovieCasting Model
    
    Alle CRUD-Operationen automatisch verfügbar
    """
    queryset = MovieCasting.objects.all()
    serializer_class = MovieCastingSerializer

🎉 Fertig!

Mit diesen 3 ViewSets haben wir bereits 18 Endpoints erstellt!

6 Endpoints × 3 Models = 18 Endpoints (list, create, retrieve, update, partial_update, destroy)

🔗 Schritt 5: Router & URLs konfigurieren

5.1

movies/urls.py erstellen (neue Datei!):

# filepath: movies/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import MovieViewSet, ArtistViewSet, MovieCastingViewSet

# Router erstellen
router = DefaultRouter()

# ViewSets registrieren
router.register(r'movies', MovieViewSet, basename='movie')
router.register(r'artists', ArtistViewSet, basename='artist')
router.register(r'castings', MovieCastingViewSet, basename='casting')

# URLs
urlpatterns = [
    path('', include(router.urls)),
]
5.2

movieapi/urls.py erweitern:

# filepath: movieapi/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('movies.urls')),  # ← NEU! API-URLs einbinden
]

🎯 Generierte URLs:

# Movies:
GET    /api/movies/           # Liste aller Filme
POST   /api/movies/           # Film erstellen
GET    /api/movies/{id}/      # Film-Details
PUT    /api/movies/{id}/      # Film aktualisieren (komplett)
PATCH  /api/movies/{id}/      # Film aktualisieren (teilweise)
DELETE /api/movies/{id}/      # Film löschen

# Artists:
GET    /api/artists/          # Liste aller Künstler
POST   /api/artists/          # Künstler erstellen
# ... (gleiche wie Movies)

# Castings:
GET    /api/castings/         # Liste aller Besetzungen
POST   /api/castings/         # Besetzung erstellen
# ... (gleiche wie Movies)

🚀 Schritt 6: Server starten & API testen

6.1

Development Server starten:

# Terminal:
python manage.py runserver

# Output:
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
January 15, 2024 - 10:00:00
Django version 4.2.7, using settings 'movieapi.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
6.2

Browsable API öffnen:

Browser öffnen: http://127.0.0.1:8000/api/

Du siehst die DRF Browsable API (HTML-Interface)

6.3

Endpoints testen:

# Browser:
http://127.0.0.1:8000/api/movies/        # ✅ Leere Liste
http://127.0.0.1:8000/api/artists/       # ✅ Leere Liste
http://127.0.0.1:8000/api/castings/      # ✅ Leere Liste

# Output (JSON):
[]

Listen sind leer, da noch keine Daten in DB

📝 Schritt 7: Erste Daten erstellen (Browsable API)

7.1

Film erstellen via Browser:

  1. Gehe zu: http://127.0.0.1:8000/api/movies/
  2. Scrolle nach unten zum HTML-Formular
  3. Fülle aus:
    title: The Matrix
    year: 1999
    genre: Sci-Fi
    rating: 8.7
    description: A computer hacker learns...
  4. Klicke "POST"
  5. ✅ Film erstellt mit ID 1
7.2

Künstler erstellen:

  1. Gehe zu: http://127.0.0.1:8000/api/artists/
  2. Formular ausfüllen:
    first_name: Keanu
    last_name: Reeves
    birth_date: 1964-09-02
    nationality: Canadian
  3. Klicke "POST"
  4. ✅ Künstler erstellt mit ID 1
7.3

Besetzung erstellen:

  1. Gehe zu: http://127.0.0.1:8000/api/castings/
  2. Formular ausfüllen:
    movie: 1 (The Matrix)
    artist: 1 (Keanu Reeves)
    role_name: Neo
    is_main_role: true
    order: 1
  3. Klicke "POST"
  4. ✅ Besetzung erstellt mit ID 1

🎨 Schritt 8: Custom Actions hinzufügen

Erweitere ViewSets mit benutzerdefinierten Endpoints

MovieViewSet erweitern:

# filepath: movies/views.py

class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    
    @action(detail=False, methods=['get'])
    def top_rated(self, request):
        """
        Custom Endpoint: GET /api/movies/top_rated/
        
        Liefert die Top 10 bestbewerteten Filme
        """
        movies = Movie.objects.filter(
            rating__isnull=False
        ).order_by('-rating')[:10]
        
        serializer = self.get_serializer(movies, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def recent(self, request):
        """
        Custom Endpoint: GET /api/movies/recent/
        
        Liefert Filme der letzten 5 Jahre
        """
        from datetime import datetime
        current_year = datetime.now().year
        movies = Movie.objects.filter(year__gte=current_year - 5)
        
        serializer = self.get_serializer(movies, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['get'])
    def castings(self, request, pk=None):
        """
        Custom Endpoint: GET /api/movies/{id}/castings/
        
        Liefert die Besetzung eines Films
        """
        movie = self.get_object()
        castings = movie.castings.all()
        serializer = MovieCastingDetailSerializer(castings, many=True)
        return Response(serializer.data)

🎭 Schritt 8.2: Custom Actions für Artists

ArtistViewSet erweitern:

# filepath: movies/views.py

class ArtistViewSet(viewsets.ModelViewSet):
    queryset = Artist.objects.all()
    serializer_class = ArtistSerializer
    
    @action(detail=True, methods=['get'])
    def filmography(self, request, pk=None):
        """
        Custom Endpoint: GET /api/artists/{id}/filmography/
        
        Liefert alle Filme eines Künstlers
        """
        artist = self.get_object()
        castings = artist.movie_roles.all()
        serializer = MovieCastingDetailSerializer(castings, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['get'])
    def movies(self, request, pk=None):
        """
        Custom Endpoint: GET /api/artists/{id}/movies/
        
        Liefert nur die Filme (ohne Casting-Details)
        """
        artist = self.get_object()
        movies = Movie.objects.filter(castings__artist=artist).distinct()
        serializer = MovieSerializer(movies, many=True)
        return Response(serializer.data)

🎯 Neue Endpoints verfügbar:

# Movies:
GET /api/movies/top_rated/       # Top 10 Filme
GET /api/movies/recent/          # Filme der letzten 5 Jahre
GET /api/movies/{id}/castings/   # Besetzung eines Films

# Artists:
GET /api/artists/{id}/filmography/  # Alle Rollen eines Künstlers
GET /api/artists/{id}/movies/       # Alle Filme eines Künstlers

🔍 Schritt 9: Filtering & Search hinzufügen

9.1

django-filter installieren:

# Terminal:
pip install django-filter

# Output:
Successfully installed django-filter-23.5
9.2

settings.py erweitern:

# filepath: movieapi/settings.py

INSTALLED_APPS = [
    # ...existing...
    'rest_framework',
    'django_filters',  # ← NEU!
    'movies',
]

# ...existing...

REST_FRAMEWORK = {
    # ...existing...
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
        'rest_framework.filters.OrderingFilter',
    ],
}
9.3

ViewSets mit Filtering erweitern:

# filepath: movies/views.py
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters

class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    
    # Filter Backends
    filter_backends = [
        DjangoFilterBackend,
        filters.SearchFilter,
        filters.OrderingFilter
    ]
    
    # Welche Felder können gefiltert werden?
    filterset_fields = ['year', 'genre']
    
    # Welche Felder können durchsucht werden?
    search_fields = ['title', 'description']
    
    # Welche Felder können sortiert werden?
    ordering_fields = ['year', 'rating', 'title']
    
    # Standard-Sortierung
    ordering = ['-year']
    
    # ...existing actions...

🔎 Filtering - Verwendung

📌 Exaktes Filtering

# Jahr 2010:
GET /api/movies/?year=2010

# Genre Sci-Fi:
GET /api/movies/?genre=Sci-Fi

# Kombiniert:
GET /api/movies/?year=2010&genre=Action

🔍 Textsuche (Search)

# Titel enthält "Matrix":
GET /api/movies/?search=Matrix

# Beschreibung enthält "action":
GET /api/movies/?search=action

📊 Sortierung (Ordering)

# Nach Jahr aufsteigend:
GET /api/movies/?ordering=year

# Nach Rating absteigend:
GET /api/movies/?ordering=-rating

# Mehrfach:
GET /api/movies/?ordering=-rating,title

🎯 Kombiniert

# Sci-Fi Filme ab 2010,
# sortiert nach Rating:
GET /api/movies/?genre=Sci-Fi&year=2010&ordering=-rating

# Suche "Matrix",
# nur ab 1999:
GET /api/movies/?search=Matrix&year=1999

🧪 Im Browser testen:

Öffne: http://127.0.0.1:8000/api/movies/?year=1999

Oben rechts im Browsable API siehst du "Filters"-Button!

⚙️ Schritt 10: Admin Interface konfigurieren

Django Admin für einfache Datenverwaltung

admin.py konfigurieren:

# filepath: movies/admin.py
from django.contrib import admin
from .models import Movie, Artist, MovieCasting


@admin.register(Movie)
class MovieAdmin(admin.ModelAdmin):
    """Admin für Movie Model"""
    list_display = ['title', 'year', 'genre', 'rating', 'created_at']
    list_filter = ['year', 'genre']
    search_fields = ['title', 'description']
    ordering = ['-year', 'title']


@admin.register(Artist)
class ArtistAdmin(admin.ModelAdmin):
    """Admin für Artist Model"""
    list_display = ['full_name', 'nationality', 'birth_date', 'created_at']
    list_filter = ['nationality']
    search_fields = ['first_name', 'last_name']
    ordering = ['last_name', 'first_name']


@admin.register(MovieCasting)
class MovieCastingAdmin(admin.ModelAdmin):
    """Admin für MovieCasting Model"""
    list_display = ['movie', 'artist', 'role_name', 'is_main_role', 'order']
    list_filter = ['is_main_role', 'movie']
    search_fields = ['role_name', 'movie__title', 'artist__last_name']
    ordering = ['order']

Superuser erstellen:

# Terminal:
python manage.py createsuperuser

# Input:
Username: admin
Email: admin@example.com
Password: ***
Password (again): ***

# Output:
Superuser created successfully.

🧪 Admin öffnen:

Browser: http://127.0.0.1:8000/admin/

Login mit: admin / dein Passwort

🧪 Schritt 11: API Testing (Command Line)

🔧 Mit cURL

# GET Liste:
curl http://127.0.0.1:8000/api/movies/

# GET Details:
curl http://127.0.0.1:8000/api/movies/1/

# POST (Film erstellen):
curl -X POST http://127.0.0.1:8000/api/movies/ \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Inception",
    "year": 2010,
    "genre": "Sci-Fi",
    "rating": "8.8"
  }'

# PUT (Film aktualisieren):
curl -X PUT http://127.0.0.1:8000/api/movies/1/ \
  -H "Content-Type: application/json" \
  -d '{
    "title": "The Matrix Reloaded",
    "year": 2003,
    "genre": "Sci-Fi",
    "rating": "7.2"
  }'

# DELETE:
curl -X DELETE http://127.0.0.1:8000/api/movies/1/

✨ Mit HTTPie (einfacher!)

# Installation:
pip install httpie

# GET Liste:
http GET http://127.0.0.1:8000/api/movies/

# GET Details:
http GET http://127.0.0.1:8000/api/movies/1/

# POST (Film erstellen):
http POST http://127.0.0.1:8000/api/movies/ \
  title="Inception" \
  year=2010 \
  genre="Sci-Fi" \
  rating=8.8

# PATCH (Teilweise Update):
http PATCH http://127.0.0.1:8000/api/movies/1/ \
  rating=9.0

# DELETE:
http DELETE http://127.0.0.1:8000/api/movies/1/

# Custom Action:
http GET http://127.0.0.1:8000/api/movies/top_rated/

# Filtering:
http GET http://127.0.0.1:8000/api/movies/ \
  year==2010 \
  ordering==-rating

📊 Komplette API Übersicht

🎬 Movies Endpoints:

GET    /api/movies/                # Liste aller Filme
POST   /api/movies/                # Film erstellen
GET    /api/movies/{id}/           # Film-Details
PUT    /api/movies/{id}/           # Film aktualisieren (komplett)
PATCH  /api/movies/{id}/           # Film aktualisieren (teilweise)
DELETE /api/movies/{id}/           # Film löschen

# Custom Actions:
GET    /api/movies/top_rated/      # Top 10 Filme
GET    /api/movies/recent/         # Filme der letzten 5 Jahre
GET    /api/movies/{id}/castings/  # Besetzung eines Films

# Filtering:
GET    /api/movies/?year=2010              # Jahr-Filter
GET    /api/movies/?genre=Sci-Fi           # Genre-Filter
GET    /api/movies/?search=Matrix          # Textsuche
GET    /api/movies/?ordering=-rating       # Sortierung

🎭 Artists Endpoints:

GET    /api/artists/               # Liste aller Künstler
POST   /api/artists/               # Künstler erstellen
GET    /api/artists/{id}/          # Künstler-Details
PUT    /api/artists/{id}/          # Künstler aktualisieren
PATCH  /api/artists/{id}/          # Künstler aktualisieren (teilweise)
DELETE /api/artists/{id}/          # Künstler löschen

# Custom Actions:
GET    /api/artists/{id}/filmography/  # Alle Rollen eines Künstlers
GET    /api/artists/{id}/movies/       # Alle Filme eines Künstlers

🎥 Castings Endpoints:

GET    /api/castings/              # Liste aller Besetzungen
POST   /api/castings/              # Besetzung erstellen
GET    /api/castings/{id}/         # Besetzung-Details
PUT    /api/castings/{id}/         # Besetzung aktualisieren
PATCH  /api/castings/{id}/         # Besetzung aktualisieren (teilweise)
DELETE /api/castings/{id}/         # Besetzung löschen

🚀 Erweiterte Features (Optional)

1. Authentifizierung

# settings.py:
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

# Installation:
pip install djangorestframework-simplejwt

# JWT für Token-basierte Auth

2. CORS (für Frontend)

# Installation:
pip install django-cors-headers

# settings.py:
INSTALLED_APPS = [
    # ...
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    # ...
]

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://localhost:8080",
]

3. API Dokumentation

# Installation:
pip install drf-spectacular

# settings.py:
INSTALLED_APPS = [
    # ...
    'drf_spectacular',
]

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 
        'drf_spectacular.openapi.AutoSchema',
}

# urls.py:
from drf_spectacular.views import (
    SpectacularAPIView,
    SpectacularSwaggerView
)

urlpatterns = [
    path('api/schema/', SpectacularAPIView.as_view()),
    path('api/docs/', SpectacularSwaggerView.as_view()),
]

4. Throttling (Rate Limiting)

# settings.py:
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day',
    }
}

💡 Best Practices

✅ Code-Struktur

  • ViewSets für Standard-CRUD
  • Custom Actions für spezielle Endpoints
  • Separate Serializer für List/Detail
  • Router für automatische URLs

⚡ Performance

  • select_related() für ForeignKeys
  • prefetch_related() für M2M
  • Pagination aktivieren
  • Nur benötigte Felder serialisieren

🔒 Sicherheit

  • Permissions setzen (Production!)
  • HTTPS in Production
  • CORS konfigurieren
  • Throttling für Rate-Limiting

📚 Dokumentation

  • Swagger/OpenAPI nutzen
  • Docstrings in Views
  • README.md mit Endpoints
  • Postman Collection erstellen

🧪 Testing

  • API-Tests schreiben
  • APITestCase nutzen
  • Alle Endpoints testen
  • Edge-Cases abdecken

🗂️ Versionierung

  • URL-Versionierung: /api/v1/
  • Header-Versionierung
  • Alte Versionen unterstützen
  • Deprecated Endpoints markieren

⚠️ Fehlerbehandlung & Validierung

Custom Validierung im Serializer:

# filepath: movies/serializers.py
from rest_framework import serializers
from django.core.exceptions import ValidationError

class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'
        read_only_fields = ['created_at', 'updated_at']
    
    def validate_year(self, value):
        """Validierung für year-Feld"""
        if value < 1888:  # Erster Film 1888
            raise serializers.ValidationError(
                "Jahr kann nicht vor 1888 sein!"
            )
        if value > 2100:
            raise serializers.ValidationError(
                "Jahr kann nicht in ferner Zukunft sein!"
            )
        return value
    
    def validate_rating(self, value):
        """Validierung für rating-Feld"""
        if value < 0 or value > 10:
            raise serializers.ValidationError(
                "Rating muss zwischen 0 und 10 sein!"
            )
        return value
    
    def validate(self, data):
        """Objekt-Level Validierung (mehrere Felder)"""
        if data.get('year') and data.get('rating'):
            # Beispiel: Alte Filme haben oft niedrigere Ratings
            if data['year'] < 1950 and data['rating'] > 9.0:
                raise serializers.ValidationError(
                    "Sehr alte Filme haben selten so hohe Ratings!"
                )
        return data

Custom Exception Handling:

# filepath: movieapi/settings.py

REST_FRAMEWORK = {
    # ...existing...
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'NON_FIELD_ERRORS_KEY': 'error',
}

🚀 Deployment Checklist

1

settings.py für Production

# filepath: movieapi/settings.py

# DEBUG aus!
DEBUG = False

# ALLOWED_HOSTS setzen
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']

# SECRET_KEY aus Environment Variable
import os
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')

# HTTPS erzwingen
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# REST Framework Production Settings
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',  # Auth required!
    ],
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',  # Nur JSON, kein Browsable API
    ],
}
2

Static Files sammeln

# settings.py:
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

# Terminal:
python manage.py collectstatic
3

Requirements freeze

# Terminal:
pip freeze > requirements.txt

# requirements.txt:
Django==4.2.7
djangorestframework==3.14.0
django-filter==23.5
django-cors-headers==4.3.1
# ...
4

Datenbank migrieren

# Production Server:
python manage.py migrate
python manage.py createsuperuser

🎯 Zusammenfassung

Was haben wir gebaut?

Eine vollständige REST-API für Movies, Artists & Castings

✅ Installiert & Konfiguriert

  • Django Rest Framework
  • django-filter
  • Settings für API
  • Router & URLs

🔄 Serializers

  • MovieSerializer
  • ArtistSerializer
  • MovieCastingSerializer
  • MovieCastingDetailSerializer

🎯 ViewSets

  • MovieViewSet (mit Actions)
  • ArtistViewSet (mit Actions)
  • MovieCastingViewSet
  • 18+ Endpoints automatisch

🎨 Custom Actions

  • top_rated
  • recent
  • castings
  • filmography

🔍 Features

  • Filtering (year, genre)
  • Search (title, description)
  • Ordering (year, rating)
  • Pagination (10 pro Seite)

⚙️ Admin & Testing

  • Django Admin konfiguriert
  • Browsable API verfügbar
  • cURL/HTTPie Commands
  • Validierung & Error Handling

🚀 Nächste Schritte

1

Testing

Unit Tests schreiben

APITestCase nutzen

Coverage > 80%

2

Authentication

JWT Tokens

Login/Logout

User Permissions

3

Frontend

React/Vue Integration

CORS konfigurieren

API Consumer bauen

4

Deployment

Docker Container

CI/CD Pipeline

Production Server

📚 Weiterführende Themen:

  • GraphQL: Alternative zu REST (mit Graphene-Django)
  • WebSockets: Real-time Updates (Django Channels)
  • Caching: Redis für Performance
  • Monitoring: Sentry für Error Tracking
  • Load Balancing: Nginx + Gunicorn

📁 Finale Projekt-Struktur

🗂️ Vollständige Ordnerstruktur:

movieapi/
├── movieapi/                      # Projekt-Konfiguration
│   ├── __init__.py
│   ├── settings.py                # ✅ DRF konfiguriert
│   ├── urls.py                    # ✅ /api/ Route
│   ├── asgi.py
│   └── wsgi.py
│
├── movies/                        # App
│   ├── migrations/
│   │   ├── __init__.py
│   │   ├── 0001_initial.py
│   │   └── ...
│   ├── __init__.py
│   ├── admin.py                   # ✅ Admin konfiguriert
│   ├── apps.py
│   ├── models.py                  # ✅ Movie, Artist, MovieCasting
│   ├── serializers.py             # ✅ NEU! 4 Serializers
│   ├── views.py                   # ✅ 3 ViewSets + Custom Actions
│   ├── urls.py                    # ✅ NEU! Router konfiguriert
│   └── tests.py                   # TODO: Tests schreiben
│
├── venv/                          # Virtuelle Umgebung
├── db.sqlite3                     # Datenbank
├── manage.py
└── requirements.txt               # ✅ Dependencies

📋 requirements.txt Inhalt:

Django==4.2.7
djangorestframework==3.14.0
django-filter==23.5
django-cors-headers==4.3.1  # Optional (für Frontend)
drf-spectacular==0.27.0     # Optional (API Docs)
httpie==3.2.2               # Optional (Testing)

📝 Code Review - Models

🎬 Movie Model Highlights:

class Movie(models.Model):
    # Felder:
    title = models.CharField(max_length=200)        # Pflichtfeld
    year = models.IntegerField()                    # Pflichtfeld
    genre = models.CharField(blank=True)            # Optional
    rating = models.DecimalField(null=True)         # Optional
    description = models.TextField(blank=True)      # Optional
    
    # Timestamps (automatisch):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    # Meta:
    class Meta:
        ordering = ['-year', 'title']  # Sortierung: Neueste zuerst
    
    # String-Repräsentation:
    def __str__(self):
        return f"{self.title} ({self.year})"
    
    # API nutzt:
    # - Auto-Timestamps für created_at/updated_at
    # - ordering für Standard-Sortierung in API
    # - __str__ für Admin & Debug

🎭 Artist Model Highlights:

class Artist(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    birth_date = models.DateField(null=True, blank=True)
    nationality = models.CharField(blank=True)
    biography = models.TextField(blank=True)
    
    # Property (nicht in DB gespeichert):
    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"
    
    # API-Nutzung:
    # - full_name Property via Serializer verfügbar
    # - ReadOnlyField() im ArtistSerializer

🎥 MovieCasting Model Highlights:

class MovieCasting(models.Model):
    # ForeignKeys (Beziehungen):
    movie = models.ForeignKey(Movie, 
                             on_delete=models.CASCADE,
                             related_name='castings')  # movie.castings.all()
    
    artist = models.ForeignKey(Artist,
                              on_delete=models.CASCADE,
                              related_name='movie_roles')  # artist.movie_roles.all()
    
    # Zusatz-Felder:
    role_name = models.CharField(max_length=200)
    is_main_role = models.BooleanField(default=False)
    order = models.IntegerField(default=0)
    
    # Unique Constraint:
    class Meta:
        unique_together = [['movie', 'artist', 'role_name']]
        # Gleicher Artist kann nicht 2x gleiche Rolle im selben Film
    
    # API-Nutzung:
    # - related_name für reverse Queries
    # - movie.castings.all() für /movies/{id}/castings/
    # - artist.movie_roles.all() für /artists/{id}/filmography/

🔄 Code Review - Serializers

🎬 MovieSerializer:

class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'  # Alle Felder aus Model
        read_only_fields = ['created_at', 'updated_at']  # Nicht änderbar
    
    # Automatisch verfügbar:
    # - id, title, year, genre, rating, description, created_at, updated_at
    # - Validierung basierend auf Model-Definitionen
    # - to_representation() für JSON-Serialisierung
    # - to_internal_value() für JSON-Deserialisierung

🎭 ArtistSerializer:

class ArtistSerializer(serializers.ModelSerializer):
    # Property aus Model als ReadOnlyField:
    full_name = serializers.ReadOnlyField()
    
    class Meta:
        model = Artist
        fields = '__all__'
        read_only_fields = ['created_at', 'updated_at']
    
    # JSON Output:
    # {
    #   "id": 1,
    #   "first_name": "Keanu",
    #   "last_name": "Reeves",
    #   "full_name": "Keanu Reeves",  ← Property!
    #   "birth_date": "1964-09-02",
    #   "nationality": "Canadian",
    #   ...
    # }

🎥 MovieCastingSerializer (Simple):

class MovieCastingSerializer(serializers.ModelSerializer):
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']
    
    # JSON Output (nur IDs):
    # {
    #   "id": 1,
    #   "movie": 1,        ← Nur ID!
    #   "artist": 1,       ← Nur ID!
    #   "role_name": "Neo",
    #   "is_main_role": true,
    #   "order": 1
    # }

🎥 MovieCastingDetailSerializer (Nested):

class MovieCastingDetailSerializer(serializers.ModelSerializer):
    # Verschachtelte Serializer:
    movie = MovieSerializer(read_only=True)
    artist = ArtistSerializer(read_only=True)
    
    class Meta:
        model = MovieCasting
        fields = '__all__'
        read_only_fields = ['created_at']
    
    # JSON Output (vollständige Objekte):
    # {
    #   "id": 1,
    #   "movie": {              ← Vollständiges Movie-Objekt!
    #     "id": 1,
    #     "title": "The Matrix",
    #     "year": 1999,
    #     ...
    #   },
    #   "artist": {             ← Vollständiges Artist-Objekt!
    #     "id": 1,
    #     "first_name": "Keanu",
    #     "last_name": "Reeves",
    #     "full_name": "Keanu Reeves",
    #     ...
    #   },
    #   "role_name": "Neo",
    #   "is_main_role": true,
    #   "order": 1
    # }
    
    # Verwendung:
    # - In Custom Action: /movies/{id}/castings/
    # - In Custom Action: /artists/{id}/filmography/

🎯 Code Review - ViewSets (Teil 1)

🎬 MovieViewSet - Basis:

class MovieViewSet(viewsets.ModelViewSet):
    # Basis-Konfiguration:
    queryset = Movie.objects.all()
    serializer_class = MovieSerializer
    
    # Filter-Backends:
    filter_backends = [
        DjangoFilterBackend,    # ?year=2010&genre=Sci-Fi
        filters.SearchFilter,   # ?search=Matrix
        filters.OrderingFilter  # ?ordering=-rating
    ]
    
    # Welche Felder filtern?
    filterset_fields = ['year', 'genre']
    # → GET /api/movies/?year=2010
    # → GET /api/movies/?genre=Sci-Fi
    # → GET /api/movies/?year=2010&genre=Action
    
    # Welche Felder durchsuchen?
    search_fields = ['title', 'description']
    # → GET /api/movies/?search=Matrix
    # → Sucht in title UND description
    
    # Welche Felder sortieren?
    ordering_fields = ['year', 'rating', 'title']
    # → GET /api/movies/?ordering=year        (aufsteigend)
    # → GET /api/movies/?ordering=-rating     (absteigend)
    # → GET /api/movies/?ordering=-year,title (mehrfach)
    
    # Standard-Sortierung (ohne ?ordering=...):
    ordering = ['-year']
    # → Neueste Filme zuerst
    
    # Automatisch verfügbare Endpoints:
    # - list()          GET    /api/movies/
    # - create()        POST   /api/movies/
    # - retrieve()      GET    /api/movies/{id}/
    # - update()        PUT    /api/movies/{id}/
    # - partial_update() PATCH  /api/movies/{id}/
    # - destroy()       DELETE /api/movies/{id}/

🎯 Code Review - Custom Actions

🎬 MovieViewSet - Custom Actions:

# @action Decorator Parameter erklärt:
#
# detail (bool) - PFLICHT:
#   • False = Collection-Aktion (ohne ID)
#     URL: /api/movies/top_rated/
#   • True = Objekt-Aktion (mit ID)
#     URL: /api/movies/{id}/castings/
#
# methods (list) - PFLICHT:
#   • ['get'] = Nur GET erlaubt
#   • ['post'] = Nur POST erlaubt
#   • ['get', 'post'] = Beide erlaubt
#   Mögliche Werte: 'get', 'post', 'put', 'patch', 'delete'
#
# url_path (str) - OPTIONAL:
#   • Standard: Methodenname
#   • Beispiel: url_path='top-rated'
#     → URL wird /api/movies/top-rated/ statt /api/movies/top_rated/
#
# url_name (str) - OPTIONAL:
#   • Standard: Methodenname
#   • Für reverse() in Django
#
# Weitere Parameter (alle OPTIONAL):
# - serializer_class = MySerializer
#   → Anderer Serializer nur für diese Action
# - permission_classes = [IsAuthenticated]
#   → Andere Permissions nur für diese Action
# - authentication_classes = [TokenAuthentication]
#   → Andere Auth nur für diese Action
# - throttle_classes = [AnonRateThrottle]
#   → Rate Limiting nur für diese Action
# - pagination_class = None
#   → Pagination deaktivieren für diese Action
# - filter_backends = [...]
#   → Andere Filter nur für diese Action


@action(detail=False, methods=['get'])
def top_rated(self, request):
    """
    Collection-Aktion (detail=False)
    
    URL: GET /api/movies/top_rated/
    
    Keine ID in URL benötigt.
    Gibt Liste der Top 10 Filme zurück.
    """
    # Filter: Nur Filme mit Rating
    # Sortierung: Rating absteigend
    # Limit: Erste 10
    movies = Movie.objects.filter(
        rating__isnull=False
    ).order_by('-rating')[:10]
    
    # Serializer mit many=True für Liste
    serializer = self.get_serializer(movies, many=True)
    
    # Response mit JSON
    return Response(serializer.data)


@action(detail=False, methods=['get'])
def recent(self, request):
    """
    Collection-Aktion (detail=False)
    
    URL: GET /api/movies/recent/
    
    Filme der letzten 5 Jahre.
    """
    from datetime import datetime
    current_year = datetime.now().year
    
    # Filter: year >= aktuelles Jahr - 5
    movies = Movie.objects.filter(year__gte=current_year - 5)
    
    serializer = self.get_serializer(movies, many=True)
    return Response(serializer.data)


@action(detail=True, methods=['get'])
def castings(self, request, pk=None):
    """
    Objekt-Aktion (detail=True)
    
    URL: GET /api/movies/{id}/castings/
    
    ID in URL erforderlich!
    Gibt Besetzung eines Films zurück.
    """
    # self.get_object() lädt Movie anhand pk
    # Wirft 404 wenn nicht gefunden
    movie = self.get_object()
    
    # Related Manager: movie.castings (aus related_name)
    castings = movie.castings.all()
    
    # Detaillierter Serializer mit Movie & Artist verschachtelt
    serializer = MovieCastingDetailSerializer(castings, many=True)
    
    return Response(serializer.data)


@action(detail=True, methods=['post'])
def add_to_favorites(self, request, pk=None):
    """
    Objekt-Aktion mit POST (detail=True, methods=['post'])
    
    URL: POST /api/movies/{id}/add_to_favorites/
    
    Ändert State (Favoriten).
    """
    movie = self.get_object()
    
    # Beispiel-Logik (Platzhalter):
    # In Realität: request.user.favorites.add(movie)
    # Oder: Favorite.objects.create(user=request.user, movie=movie)
    
    # Response mit Status-Info
    return Response({
        'status': 'success',
        'message': 'Film zu Favoriten hinzugefügt',
        'movie': {
            'id': movie.id,
            'title': movie.title
        }
    }, status=status.HTTP_200_OK)

🎭 Code Review - ArtistViewSet

🎭 ArtistViewSet - Custom Actions:

class ArtistViewSet(viewsets.ModelViewSet):
    queryset = Artist.objects.all()
    serializer_class = ArtistSerializer
    
    @action(detail=True, methods=['get'])
    def filmography(self, request, pk=None):
        """
        Objekt-Aktion (detail=True)
        
        URL: GET /api/artists/{id}/filmography/
        
        Liefert alle Rollen eines Künstlers
        mit vollständigen Movie & Artist Objekten.
        """
        # Künstler laden
        artist = self.get_object()
        
        # Related Manager: artist.movie_roles (aus related_name)
        # → Alle MovieCasting-Objekte für diesen Artist
        castings = artist.movie_roles.all()
        
        # Detaillierter Serializer
        serializer = MovieCastingDetailSerializer(castings, many=True)
        
        # JSON Response:
        # [
        #   {
        #     "id": 1,
        #     "movie": { "id": 1, "title": "The Matrix", ... },
        #     "artist": { "id": 1, "full_name": "Keanu Reeves", ... },
        #     "role_name": "Neo",
        #     "is_main_role": true,
        #     "order": 1
        #   },
        #   {
        #     "id": 5,
        #     "movie": { "id": 3, "title": "John Wick", ... },
        #     "artist": { "id": 1, "full_name": "Keanu Reeves", ... },
        #     "role_name": "John Wick",
        #     "is_main_role": true,
        #     "order": 1
        #   }
        # ]
        return Response(serializer.data)
    
    @action(detail=True, methods=['get'])
    def movies(self, request, pk=None):
        """
        Objekt-Aktion (detail=True)
        
        URL: GET /api/artists/{id}/movies/
        
        Liefert nur die Filme (ohne Casting-Details).
        Einfachere Alternative zu filmography.
        """
        artist = self.get_object()
        
        # Query über MovieCasting-Beziehung
        # distinct() vermeidet Duplikate (wenn Artist mehrere Rollen im Film)
        movies = Movie.objects.filter(
            castings__artist=artist
        ).distinct()
        
        # Nur Movie-Serializer (nicht MovieCastingDetailSerializer)
        serializer = MovieSerializer(movies, many=True)
        
        # JSON Response:
        # [
        #   { "id": 1, "title": "The Matrix", "year": 1999, ... },
        #   { "id": 3, "title": "John Wick", "year": 2014, ... }
        # ]
        return Response(serializer.data)

🔍 Unterschied: filmography vs movies

  • filmography: Vollständige Casting-Infos (Rolle, Hauptrolle, Reihenfolge)
  • movies: Nur Film-Infos (Title, Jahr, Genre, etc.)
  • Use Case filmography: "In welchen Rollen hat Keanu Reeves gespielt?"
  • Use Case movies: "In welchen Filmen hat Keanu Reeves gespielt?"

🧪 API Testing - Praktische Beispiele

📊 Komplexe Queries kombinieren:

# 1. Sci-Fi Filme ab 2010, sortiert nach Rating, nur Top 5
GET /api/movies/?genre=Sci-Fi&year__gte=2010&ordering=-rating&page_size=5

# Query-Parameter erklärt:
# - genre=Sci-Fi           → Filterset: genre field
# - year__gte=2010         → Django ORM Lookup: year >= 2010
# - ordering=-rating       → OrderingFilter: Rating absteigend
# - page_size=5            → Pagination: nur 5 Ergebnisse


# 2. Suche nach "Matrix" in Title/Description
GET /api/movies/?search=Matrix

# SearchFilter durchsucht:
# - title (icontains)
# - description (icontains)


# 3. Künstler mit "Reeves" im Namen
GET /api/artists/?search=Reeves

# Findet:
# - first_name oder last_name enthält "Reeves"


# 4. Kombiniert: Sci-Fi Filme, Suche "space", sortiert Jahr
GET /api/movies/?genre=Sci-Fi&search=space&ordering=-year

# Logik:
# 1. Filter: genre = Sci-Fi
# 2. Search: "space" in title OR description
# 3. Order: year absteigend (neueste zuerst)

🎯 Custom Actions testen:

# Top 10 Filme
GET /api/movies/top_rated/
# Response: [{ "id": 1, "title": "...", "rating": "9.5" }, ...]

# Filme der letzten 5 Jahre
GET /api/movies/recent/
# Response: [{ "id": 5, "year": 2023, ... }, ...]

# Besetzung von Film ID 1
GET /api/movies/1/castings/
# Response: [
#   {
#     "id": 1,
#     "movie": { "id": 1, "title": "The Matrix", ... },
#     "artist": { "id": 1, "full_name": "Keanu Reeves", ... },
#     "role_name": "Neo",
#     "is_main_role": true
#   }
# ]

# Filmographie von Artist ID 1
GET /api/artists/1/filmography/
# Response: Alle Rollen von Keanu Reeves

# Nur Filme von Artist ID 1
GET /api/artists/1/movies/
# Response: Nur Film-Objekte (ohne Casting-Details)

⚡ Performance Optimierung

N+1 Query Problem vermeiden

select_related & prefetch_related nutzen

❌ Ineffizient (N+1 Problem)

# views.py - SCHLECHT:
@action(detail=True, methods=['get'])
def castings(self, request, pk=None):
    movie = self.get_object()
    castings = movie.castings.all()  # 1 Query
    
    serializer = MovieCastingDetailSerializer(
        castings, many=True
    )
    return Response(serializer.data)

# Problem:
# 1 Query: movie.castings.all()
# N Queries: Für jedes Casting → movie laden
# N Queries: Für jedes Casting → artist laden
# 
# Bei 10 Castings: 1 + 10 + 10 = 21 Queries!
# → Sehr langsam!

✅ Optimiert (Eager Loading)

# views.py - GUT:
@action(detail=True, methods=['get'])
def castings(self, request, pk=None):
    movie = self.get_object()
    
    # select_related() für ForeignKeys:
    castings = movie.castings.select_related(
        'movie',    # ForeignKey
        'artist'    # ForeignKey
    ).all()
    
    serializer = MovieCastingDetailSerializer(
        castings, many=True
    )
    return Response(serializer.data)

# Optimiert:
# 1 Query mit JOINs:
# SELECT * FROM movie_casting
# INNER JOIN movies ON (movie_casting.movie_id = movies.id)
# INNER JOIN artists ON (movie_casting.artist_id = artists.id)
# WHERE movie_casting.movie_id = 1
#
# Bei 10 Castings: nur 1 Query!
# → Sehr schnell!

🚀 Weitere Optimierungen:

# 1. Queryset auf ViewSet-Level optimieren:
class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.select_related('genre').all()
    serializer_class = MovieSerializer
    
    # Alle Movie-Queries nutzen jetzt select_related()


# 2. prefetch_related() für Reverse ForeignKeys & M2M:
class MovieViewSet(viewsets.ModelViewSet):
    queryset = Movie.objects.prefetch_related('castings').all()
    
    @action(detail=True, methods=['get'])
    def castings(self, request, pk=None):
        movie = self.get_object()
        # castings sind bereits geprefetched!
        castings = movie.castings.select_related(
            'artist'
        ).all()
        serializer = MovieCastingDetailSerializer(
            castings, many=True
        )
        return Response(serializer.data)


# 3. only() / defer() für große Felder:
class MovieViewSet(viewsets.ModelViewSet):
    def list(self, request):
        # Nur benötigte Felder laden (nicht description):
        movies = Movie.objects.only(
            'id', 'title', 'year', 'rating'
        ).all()
        serializer = self.get_serializer(movies, many=True)
        return Response(serializer.data)


# 4. Pagination nutzen (bereits konfiguriert):
# → Nur 10 Einträge pro Request
# → Verhindert zu große Responses


# 5. Database Indexing:
class Movie(models.Model):
    year = models.IntegerField(db_index=True)  # Index!
    genre = models.CharField(db_index=True)    # Index!
    
    class Meta:
        indexes = [
            models.Index(fields=['year', 'genre']),  # Composite Index
        ]

🔒 Sicherheit & Best Practices

1. Permissions

# settings.py (Production):
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ],
}

# ViewSet-Level:
class MovieViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]
    
# Action-Level:
@action(detail=True, 
        methods=['post'],
        permission_classes=[IsAdminUser])
def delete_permanently(self, request, pk=None):
    # Nur Admins können löschen
    pass

2. Validierung

# serializers.py:
class MovieSerializer(serializers.ModelSerializer):
    def validate_year(self, value):
        if value < 1888:
            raise ValidationError(
                "Jahr kann nicht vor 1888 sein!"
            )
        return value
    
    def validate(self, data):
        # Multi-Field Validierung
        if data['year'] > 2100:
            raise ValidationError(
                "Zu weit in Zukunft!"
            )
        return data

3. Rate Limiting

# settings.py:
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day',
    }
}

# Custom Throttle:
class BurstRateThrottle(UserRateThrottle):
    rate = '60/min'

4. CORS

# settings.py:
INSTALLED_APPS = [
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
]

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "https://yourdomain.com",
]

5. HTTPS (Production)

# settings.py:
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

6. Logging

# settings.py:
LOGGING = {
    'version': 1,
    'handlers': {
        'file': {
            'level': 'ERROR',
            'class': 'logging.FileHandler',
            'filename': 'errors.log',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'ERROR',
        },
    },
}

🎉 Geschafft!

Du hast eine vollständige REST-API gebaut!

✨ Was du erreicht hast:

  • ✅ Django Rest Framework installiert & konfiguriert
  • ✅ 3 Models mit Beziehungen erstellt
  • ✅ 4 Serializers gebaut (inkl. Nested Serializers)
  • ✅ 3 ViewSets mit 18+ Endpoints
  • ✅ 5 Custom Actions für spezielle Funktionen
  • ✅ Filtering, Search & Ordering implementiert
  • ✅ Pagination konfiguriert
  • ✅ Admin Interface eingerichtet
  • ✅ API Testing durchgeführt
  • ✅ Performance optimiert
  • ✅ Security Best Practices gelernt

🚀 Nächste Level:

  • 🔐 Authentication: JWT, OAuth2
  • 🧪 Testing: Unit Tests, Integration Tests
  • 📱 Frontend: React/Vue Consumer bauen
  • 🐳 Deployment: Docker, CI/CD, Cloud
  • 📊 Monitoring: Sentry, Prometheus
  • Real-time: WebSockets mit Django Channels

Keep coding! 💻🎯

Happy API Building! 🚀

1 / 34